package com.xmlit.project.engine.xsd;

import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.ws.jaxme.xs.XSAnnotation;
import org.apache.ws.jaxme.xs.XSAppinfo;
import org.apache.ws.jaxme.xs.XSElement;
import org.apache.ws.jaxme.xs.XSEnumeration;
import org.apache.ws.jaxme.xs.XSGroup;
import org.apache.ws.jaxme.xs.XSParser;
import org.apache.ws.jaxme.xs.XSParticle;
import org.apache.ws.jaxme.xs.XSSchema;
import org.apache.ws.jaxme.xs.impl.XSObjectImpl;
import org.apache.ws.jaxme.xs.xml.XsTElement;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.xmlit.project.engine.struct.Struct;
import com.xmlit.project.engine.struct.StructSequence;
import com.xmlit.project.engine.struct.StructSimple;
import com.xmlit.project.engine.struct.impl.StructChoiceImpl;
import com.xmlit.project.engine.struct.impl.StructSequenceImpl;
import com.xmlit.project.engine.struct.impl.StructSimpleImpl;

public class XSD2Struct {
	public static Struct xsd2Struct(String schemaString) throws Exception,
			SAXException, ParserConfigurationException {
		StringReader schema = new StringReader(schemaString);
		return xsd2Struct(schema);
	}

	public static Struct xsd2Struct(Reader schema) throws Exception,
			SAXException, ParserConfigurationException {
		InputSource source = new InputSource(schema);
		XSParser xsParser = new XSParser();
		xsParser.setValidating(false);
		XSSchema xsSchema = null;
		try {
			xsSchema = xsParser.parse(source);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		XSElement root = xsSchema.getElements()[0];
		return readElement(root);
	}

	public static Struct readElement(XSElement element) throws Exception {
		Struct current = null;
		if (element.getType().isSimple()) {
			current = new StructSimpleImpl(element.getName().getLocalName());
			if (element.getType().getSimpleType().getPattern() != null) {
				String pattern = element.getType().getSimpleType().getPattern()[0][0];
				((StructSimple) current)
						.setSimplePattern(handleUnEscape(pattern));
			} else {
				StringBuffer sb = new StringBuffer();
				for (XSEnumeration enu:element.getType().getSimpleType().getEnumerations()) {
					sb.append(enu.getValue() + "\n");
				}
				((StructSimple) current).setAllowedValues(sb.toString());
			}
		} else {
			XSGroup group = element.getType().getComplexType().getParticle()
					.getGroup();

			if ("sequence".equals("" + group.getCompositor())) {
				current = new StructSequenceImpl(element.getName()
						.getLocalName());
			} else if ("choice".equals("" + group.getCompositor())) {
				current = new StructChoiceImpl(element.getName().getLocalName());
			} else if ("all".equals("" + group.getCompositor())) {
				throw new RuntimeException("'all' is not supported yet");
			}

			for (XSParticle child : group.getParticles()) {
				if (child.isElement())
					current.addChild(readElement(child.getElement()));
			}
		}
		XsTElement xele = getXsTElement(element);
		current.setMinOccurrences(xele.getMinOccurs());

		if (xele.getMaxOccurs() == -1) {
			current.setMaxOccurrences(100);
		} else {
			current.setMaxOccurrences(xele.getMaxOccurs());
		}
		handleAnnotation(current, element);

		return current;
	}

	private static boolean handleAnnotation(Struct current, XSElement element) {
		Document annotation = getAnnotationDocuments(element);
		if (annotation != null) {
			String prefix = annotation.getDocumentElement().getAttribute(
					"prefix");
			current.setPrefix(handleUnEscape(prefix));
			String suffix = annotation.getDocumentElement().getAttribute(
					"suffix");
			current.setSuffix(handleUnEscape(suffix));

			if (current instanceof StructSequence) {
				String delimiter = annotation.getDocumentElement()
						.getAttribute("delimiter");
				((StructSequence) current)
						.setDelimiter(handleUnEscape(delimiter));
				String lookahead = annotation.getDocumentElement()
						.getAttribute("lookahead");
				((StructSequence) current)
						.setLookahead(handleUnEscape(lookahead));
				String not = annotation.getDocumentElement()
						.getAttribute("not");
				((StructSequence) current).setNegative("true".equals(not));
			} else if (current instanceof StructSimple) {
				((StructSimple) current).setAllowedChars(annotation
						.getDocumentElement().getAttribute("allowedChars"));

				((StructSimple) current).setMinLength(Integer
						.parseInt(annotation.getDocumentElement().getAttribute(
								"minLength")));

				((StructSimple) current).setMaxLength(Integer
						.parseInt(annotation.getDocumentElement().getAttribute(
								"maxLength")));

				if (annotation.getDocumentElement()
						.getAttribute("allowedChars") != null
						&& !"".equals(annotation.getDocumentElement()
								.getAttribute("allowedChars"))) {
					((StructSimple) current).setSimplePattern(null);
				}
			}
		}

		return true;

	}

	private static Document getAnnotationDocuments(XSElement element) {
		if (element.getAnnotations().length > 0) {
			XSAnnotation xsAnnotation = element.getAnnotations()[0];
			XSAppinfo[] xsAppinfos = xsAnnotation.getAppinfos();
			XSAppinfo xsAppinfo = xsAppinfos[0];

			for (int i = 0; i < xsAppinfo.getChilds().length; i++) {
				if (xsAppinfo.getChilds()[i] instanceof Document)
					return (Document) xsAppinfo.getChilds()[i];
			}
		}
		return null;

	}

	private static XsTElement getXsTElement(XSElement element) {
		XsTElement base = null;
		try {
			Field field = XSObjectImpl.class.getDeclaredField("baseObject");
			field.setAccessible(true);
			base = (XsTElement) field.get(element);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return base;
	}

	private static String handleUnEscape(String attribute) {
		if (attribute == null) {
			return null;
		}
		String result = attribute.replace("\r", "\\r");
		result = result.replace("\n", "\\n");

		return result;
		// return attribute;
	}
}
